using System;
using System.Text;
using System.Threading;
using System.Collections.Generic;
#if LINUX
using DBus;
#else
using System.ServiceModel;
using System.ServiceModel.Description;
#endif
namespace repragator
{
	public delegate void FinishedParsingHandler();
	public delegate void DebugMessageHandler(string message);
	
	[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
	public class Printer : IPrinter
	{
		public event DebugMessageHandler DebugMessageEvent;
		public event FinishedParsingHandler FinishedParsingEvent;
		
		public List<Layer> RenderableGCode
		{
			get
			{
				return this.parser.RenderableGCode;
			}
		}
		
		public string[] AvailablePorts
		{
			get
			{
				return this.serial.ScanPorts();	
			}
		}
		
		public bool Online
		{
			get
			{
				return this.serial.IsConnect;		
			}
		}
		
		public int LayerCount
		{
			get
			{
				return this.RenderableGCode.Count;		
			}
		}
		
		private Serial serial;
		private DateTime start;
		private Position position;
		private ServiceHost host;
		private GCodeParser parser;
		private Thread printThread;
		private bool printing = false;
		private Stack<string> gcodeStack;
		private Thread monitorTemperature;
		
		public Printer ()
		{
			try
			{
				this.serial = new Serial();
				
				this.serial.DebugMessageEvent += new DebugMessageHandler(this.DebugMessage);
				
				this.position = new Position(0, 0, 0);
				
				this.parser = new GCodeParser();
			
				this.parser.DebugMessageEvent += new DebugMessageHandler(this.DebugMessage);
				
				this.monitorTemperature = new Thread(new ThreadStart(this.MonitorTemperatureThread));
#if LINUX
				if (Bus.Session.RequestName("org.repragator.printer") == org.freedesktop.DBus.RequestNameReply.PrimaryOwner)
				{
					Bus.Session.Register(new ObjectPath("/org/repragator/printer"), this);
				}
				
				BusG.Init();
#else
				host = new ServiceHost(this, new Uri("http://10.0.6.140:8081"));
				
				ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
				
				smb.HttpGetEnabled = true;
				
				host.Description.Behaviors.Add(smb);
				
				host.AddServiceEndpoint(typeof(IPrinter), new BasicHttpBinding(), "Printer");
				
				host.Open();
#endif
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);
			}
		}
	
		public bool Connect(string port, int baud)
		{
			try
			{
				bool result;
				
				result = this.serial.Connect(port, baud);
				
				if (result)
					this.monitorTemperature.Start();
				
				return result;
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);
				
				return false;
			}
		}
		
		public void Disconnect()
		{
			try
			{
				this.monitorTemperature.Abort();
				
				this.serial.Disconnect();
				
				this.host.Abort();
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		public void Load(string filename)
		{
			try
			{
				this.parser.Parse(filename);
				
				new Thread(new ThreadStart(this.FinishedParsingEvent)).Start();
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);
			}
		}
		
		public void Move(string axis, double distance, int feedrate)
		{
			try
			{
				double position = 0;
				string cmd = string.Empty;
				
				if (axis.ToLower() == "h")
				{
					this.DebugMessage("Homing all axis's");
					
					cmd = "G28";
				}
				else if (distance == -999)
				{
					this.DebugMessage(string.Format("Homing {0} axis", axis));
					
					cmd = string.Format("G28 {0}", axis.ToUpper());
				}
				else
				{
					switch (axis.ToLower())
					{
					case "x":
						//if (this.position.X + distance <= 0) throw new Exception("Negative movement prevented");
						position = this.position.X += distance;
						break;
					case "y":
						//if (this.position.Y + distance <= 0) throw new Exception("Negative movement prevented");
						position = this.position.Y += distance;
						break;
					case "z":
						//if (this.position.Z + distance <= 0) throw new Exception("Negative movement prevented");
						position = this.position.Z += distance;
						break;
					}
					
					this.DebugMessage(string.Format("Moving {0} {1} mm at {2} mm/min", axis, distance, feedrate));
					
					cmd = string.Format("G1 {0}{1} F{2}", axis.ToUpper(), distance, feedrate);
				}
				
				this.serial.Send("G91");
				
				this.serial.Send(cmd);
				
				this.serial.Send("G90");
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		public void Extrude(double distance, int feedrate)
		{
			try
			{
				string cmd;
				
				cmd = string.Format("G1 E{0} F{1}", distance, feedrate);
				
				this.DebugMessage(string.Format("Extruding {0} mm at {1} mm/min", distance, feedrate));
				
				this.serial.Send("G91");
				
				this.serial.Send(cmd);
				
				this.serial.Send("G90");
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);
			}
		}
		
		public void HotendTemp(int temp)
		{
			try
			{
				string cmd;
				
				cmd = string.Format("M104 S{0}", temp);
				
				this.serial.Send(cmd);
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		public void BedTemp(int temp)
		{
			try
			{
				string cmd;
				
				cmd = string.Format("M140 S{0}", temp);
				
				this.serial.Send(cmd);
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		public void DisableMotors()
		{
			try
			{				
				this.serial.Send("M18");
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		public void SendCmd(string cmd)
		{
			try
			{
				this.serial.Send(cmd);
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		public void MonitorTemperatureThread()
		{
			try
			{
				Thread.Sleep(5 * 1000);
				
				while (this.serial.IsConnect)
				{
					this.serial.Send("M105");
					
					Thread.Sleep(1000);
				}
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		public void Print()
		{
			try
			{
				this.printing = true;
				
				this.gcodeStack = new Stack<string>(this.parser.RawGCode);
			
				this.printThread = new Thread(new ThreadStart(this.PrintThread));
				
				this.printThread.Start();
		
				this.start = DateTime.Now;				
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		private void PrintThread()
		{	
			string nextCmd;
			
			while (this.printing)
			{
				nextCmd = this.gcodeStack.Pop();
				
				this.serial.Send(nextCmd);
			
				Thread.Sleep(100);
			}
		}
		
		public void Pause()
		{
			this.printing = false;
		}
		
		public void Resume()
		{
			try
			{
				this.printing = true;
				
				this.printThread = new Thread(new ThreadStart(this.PrintThread));
					
				this.printThread.Start();
			}
			catch (Exception e)
			{
				this.DebugMessage(e.Message + "\n" + e.StackTrace);	
			}
		}
		
		public void Stop()
		{
			this.printing = false;
		}
		
		public void DebugMessage(string message)
		{
			if (this.DebugMessageEvent != null)
				this.DebugMessageEvent(message);
		}
	}
}